// Initial File - Paul Jack

#include <QApplication>
#include <QStringList>
#include "usbmain.h"
#include "usbdevice.h"

// Jungo Defines
#include <status_strings.h>

#define JUNGO_LICENSE_STRING    "6c3cd578742eae8c1c20b0e025596c35af386f68.DRS"
const int MATCH_TABLE_COUNT = 3;

#define UNUSED(p) ((void)(p)) //BL

// This is a file used during the initialization to provide Vendor IDs and Product IDs.

#define DEFAULT_MATCH_FILE "usb.ini"



/*!
 * Class UsbAttachEvent
 *
 * Description: 
 * This class is used to signal the application that a device
 * has attached.  We use the QEvent to allow this notification
 * to enter the application's message queue since we will be
 * running from a separate thread.
 *
 */
class UsbAttachEvent : public QEvent
{
public:
    UsbAttachEvent( UsbDevice *device ):
        QEvent((QEvent::Type)EUsbAttachEvent) 
    {
        mDevice = device;
    };


    UsbDevice *mDevice;

};


/*!
 * Class UsbDetachEvent
 *
 * Description: 
 * This class is used to signal the application that a device
 * has detached.  We use the QEvent to allow this notification
 * to enter the application's message queue since we will be
 * running from a separate thread.
 *
 */
class UsbDetachEvent : public QEvent
{
public:
    UsbDetachEvent( UsbDevice *device ) :
        QEvent((QEvent::Type)EUsbDetachEvent) 
    {
        mDevice = device;
    };

 
    UsbDevice *mDevice;
};






/*!
 * DeviceAttach 
 *
 * Description: 
 * This is a callback function.  We pass the handle of this
 * function into the device driver during initialization.
 * When the driver notices that a radio matching our match table
 * has attached it will call this function.
 *
 * @param hDevice is of type WDU_DEVICE_HANDLE 
 * @param *pDeviceInfo is of type WDU_DEVICE 
 * @param pUserData is of type PVOID 
 *
 * @returns BOOL DLLCALLCONV 
 */
BOOL DLLCALLCONV DeviceAttach( WDU_DEVICE_HANDLE hDevice, WDU_DEVICE *pDeviceInfo, PVOID pUserData)
{
    UsbDevice *device = new UsbDevice;

	UNUSED(pUserData);	 //BL
    // We are only supporting one active interface per device.
    // And we need to watch out for different versions of Jungo

#if WD_MAJOR_VER == 8
    if(pDeviceInfo->pActiveInterface[0] != NULL)
    {
        device->mInterfaceNum = pDeviceInfo->pActiveInterface[0]->pActiveAltSetting->Descriptor.bInterfaceNumber;
        device->mAlternateSetting = pDeviceInfo->pActiveInterface[0]->pActiveAltSetting->Descriptor.bAlternateSetting;
#else
    if(pDeviceInfo->pActiveInterface != NULL)
    {
        device->mInterfaceNum = pDeviceInfo->pActiveInterface->pActiveAltSetting->Descriptor.bInterfaceNumber;
        device->mAlternateSetting = pDeviceInfo->pActiveInterface->pActiveAltSetting->Descriptor.bAlternateSetting;
#endif        
        device->mhDevice = hDevice;
        device->mVendorId = pDeviceInfo->Descriptor.idVendor;
        device->mProductId = pDeviceInfo->Descriptor.idProduct;

#ifdef USE_USB_KLUDGE
// Kludge to fix the missing serial number issue.
// We'll allow 2 radios for the moment and roll
// the sn so we can fake the re-attach if needed.
#pragma message("WARNING WARNING WARNING WARNING WARNING")
#pragma message("USBMAIN.cpp: This has GOT TO BE FIXED!!!")
#pragma message("WARNING WARNING WARNING WARNING WARNING")

        static int x = 0;
        
        if( x > 2)
        {
            x = 1;
        }
        
        device->mIdentification = pDeviceInfo->Descriptor.iSerialNumber;
        device->mIdentification.sprintf("%d.00", x);
		x++;

#else
        // Here's the routine to retrieve the serial number
        // Note that the string is actually coded in words.
        // Each char followed by a null.  This means that to get
        // to the entire string we have to take every other char.
        if(pDeviceInfo->Descriptor.iSerialNumber > 0)
        {
            // TODO: Add this in when the SI-9479 get's a real serial number!
            unsigned char srn[32];
            unsigned char productId[32];
            unsigned long dwSize;

            WDU_GetStringDesc(hDevice, pDeviceInfo->Descriptor.iProduct, &productId[0], 32, 0, &dwSize);
            QString productIdentification;
            for(unsigned long i = 0; i < dwSize; i+=2)
            {
                // Is this the null terminator for the string?
                // If it is then don't continue appending NULL
                // to the end of the string!
                if(productId[i] == '\0')
                {
                    break;
                }
                productIdentification += productId[i];
            }
            qDebug("Product Identification: %s", productIdentification.toLocal8Bit().constData());
            device->mDescription = productIdentification;


            WDU_GetStringDesc(hDevice, pDeviceInfo->Descriptor.iSerialNumber, &srn[0], 32, 0, &dwSize);
            QString serNo;

            for(unsigned long i = 0; i < dwSize; i+=2)
            {
                // Is this the null terminator for the string?
                // If it is then don't continue appending NULL
                // to the end of the string!
                if(srn[i] == '\0')
                {
                    break;
                }
                serNo += srn[i];
            }
            qDebug("Serno: %s", serNo.toLocal8Bit().constData());

            device->mIdentification = serNo;
        }
#endif


        qApp->postEvent( Usb::getInstance(), new UsbAttachEvent( device ) );
    }
    
    // Accept control over this device
    return true;
}



/*!
 * DeviceDetach 
 *
 * Description: 
 * This is a callback function.  We pass the handle of this
 * function into the device driver during initialization.
 * When the driver notices that a radio matching our match table
 * has detached it will call this function.

 * @param hDevice is of type WDU_DEVICE_HANDLE 
 * @param pUserData is of type PVOID 
 *
 * @returns VOID DLLCALLCONV 
 */
VOID DLLCALLCONV DeviceDetach( WDU_DEVICE_HANDLE hDevice, PVOID pUserData )
{
    UsbDevice *device = new UsbDevice;
	UNUSED(pUserData);  //BL
    device->mhDevice = hDevice;
    
    qApp->postEvent( Usb::getInstance(), new UsbDetachEvent( device ) );
}


//=============================================================================
// The following is the USB Handling routines!  These are the routines
// that will be running in a separate thread.  We have to be aware of
// thread affinity which is why we use the UsbDetach/AttachEvents.


// This is the single USB instance
Usb* Usb::mInstance = NULL;



/*!
 * Usb::Usb 
 *
 * Description: 
 * Private Constructor.  The user needs to use the Get Instance.
 *
 * @returns None.
 */
Usb::Usb()
{
    mMatchTable = NULL;
}




/*!
 * Usb::~Usb 
 *
 * Description:
 * Default Destructor
 *
 *
 * @returns None.
 */
Usb::~Usb()
{
    WDU_Uninit(mhDriver);

    if(mInstance)
    {
        delete mInstance;
        mInstance = NULL;
    }

    if(mMatchTable)
    {
        delete [] mMatchTable;
    }
}




/*!
 * Usb::event 
 *
 * Description:
 * This is the event handler which will be called from the
 * callback functions listed above.  When a device attaches/detaches
 * an event class will be created and passed to the event handler.
 * This method is where those events pop out.
 *
 * Note that the callbacks are happening in global space and this 
 * event handler will be in application space.
 *
 * @param e is of type QEvent* 
 *
 * @returns bool 
 */
bool Usb::event( QEvent* e )
{
    bool handled = false;

    switch(e->type())
    {
        case EUsbAttachEvent:
        {
            qDebug("Attach Event");
            // Check to see if that event is valid.
            UsbAttachEvent *pEvent;
            pEvent = ( UsbAttachEvent * ) (e);

            if(pEvent)
            {
                // add this device to the list.
                if(isValidDevice(pEvent->mDevice) == false)
                {
                    pEvent->mDevice->setConnected(true);
                    mDeviceList.append(pEvent->mDevice);

                
                    // Notify the user that the device list has changed.
                    emit deviceAttached(true);
                    emit deviceListChanged(true, mDeviceList);
                }
            }
            else
            {
                qWarning("Usb::event An unexpected event was ""received.");
            }

            handled = true;

            break;
        }

        case EUsbDetachEvent:
        {
            qDebug("Detach Event");
            // Check to see if that event is valid.
            UsbDetachEvent *pEvent = ( UsbDetachEvent * ) (e);

            if(pEvent)
            {
                // Go through all the devices in the list and remove this one.
                QList < UsbDevice > ::iterator it;
                for(int index = 0; index < (int)mDeviceList.count(); index++)
                {
                    if(mDeviceList[index]->mhDevice == pEvent->mDevice->mhDevice)
                    {
                        mDeviceList[index]->setConnected(false);
                        mDeviceList.removeAt(index);
                    
                        emit deviceAttached(false);
                        emit deviceListChanged (false, mDeviceList);
                        break; // only one device per message.
                    }
                }
            }
            else
            {
                qWarning("RUsbFactory::event An unexpected event was ""received.");
            }

            handled =  true;
        }

        default:
        {
            handled = false;
        }
    }
    
    return handled;
}





/*!
 * Usb::getInstance 
 *
 * Description: 
 * Returns the Singleton USB Instance.  If not created
 * it will call the constructor first.
 *
 * @returns Usb * 
 */
Usb * Usb::getInstance(void)
{
    if(!mInstance)
    {
        mInstance = new Usb;
        mInstance->init();
    }

    return mInstance;
}




/*!
 * *> Usb::getDeviceList 
 *
 * Description: 
 * Returns the list of currently attached devices.
 *
 * @returns QList < UsbDevice 
 */
QList < UsbDevice *> Usb::getDeviceList()
{
    return mDeviceList;
}

                


/*!
 * Usb::init 
 *
 * Description: 
 * Spins up the USB driver.  Assigns the Vendor IDs and Product IDs
 * to the driver as well as provides the callback routines.
 *
 * @returns DWORD 
 * WD_INVALID_PARAMETER if there is an init file that does not contain
 * usable IDs.
 * Refer to the Jungo USB User's Guide/Manual for information on 
 * WDU_Init and the various return codes it can provide.
 */
DWORD Usb::init( void )
{

    DWORD error = WD_INVALID_PARAMETER;
    
    WDU_EVENT_TABLE eventTable;
    mhDriver = 0;

    // Load our match Tables
    int matchCount = loadMatchTable();

    if(matchCount)
    {

        BZERO(eventTable);
        eventTable.pfDeviceAttach = DeviceAttach;
        eventTable.pfDeviceDetach = DeviceDetach;
        eventTable.pUserData = 0;

        QString licenseString;
    

        error = WDU_Init(&mhDriver, mMatchTable, matchCount, &eventTable, JUNGO_LICENSE_STRING, WD_ACKNOWLEDGE);
//        if(error)
//        {
//            qWarning("RUsbFactory::RUsbFactory(): failed to initialize USB "
//                     "driver 0070: error 0x%lx (\"%s\")", error, Stat2Str(error));
//        }
    
    }

    return error;
}




/*!
 * Usb::loadMatchTable 
 *
 * Description:
 * This method will load the Product ID and Vendor ID from a file if available;
 * Otherwise it simply uses our existing defaults.
 * This allows us to expand our list without having to constantly recompile 
 * the application.
 *
 *
 * @returns int matches = number of devices in the match table
 */
int Usb::loadMatchTable( void )
{
    QFile matchFile(DEFAULT_MATCH_FILE);
    QStringList matchList;
    int entries;             
    quint16 pid;
    quint16 vid;
    bool okPid;
    bool okVid;

    entries = 0;

    if(matchFile.open ( QIODevice::ReadOnly ))
    {
        // Load from the file
        QString data = matchFile.readAll();
        QStringList list = data.split("\n");

        for(int i = 0; i < list.count(); i++)
        {
            if(!(list.at(i).startsWith(";")))
            {
                QStringList line = list.at(i).split(":");
                if(line.count() == 2)
                {
                    pid = line.at(0).toUInt(&okPid, 16);
                    vid = line.at(1).toUInt(&okVid, 16);

                    //qDebug("Pid: %p", pid);
                    //qDebug("Vid: %p", vid);

                    if(okPid && okVid)
                    {   matchList << line.at(0);
                        matchList << line.at(1);
                        entries++;
                    }
                }
            }
        }
    }
    else
    {
        // Load our defaults
        // GreyFox 70 
        matchList << "0xFFFF";
        matchList << "0x0070";
        entries++;

        // GreyFox 140
        matchList << "0xFFFF";
        matchList << "0x0140";
        entries++;

        // Default Pico everything
        matchList << "0x1959";
        matchList << "0x9479";
        entries++;
    }

    // Build up our match table with our match list;
    mMatchTable = new WDU_MATCH_TABLE[entries];
    for(int i = 0; i < matchList.count(); i+=2)
    {
        // Clear out this table
        BZERO(mMatchTable[i/2]);

        vid = matchList.at(i).toUInt(&okVid, 16);
        pid = matchList.at(i+1).toUInt(&okPid, 16);

        if(okPid && okVid)
        {
            qDebug("Loading USB Vendor ID: %p Product ID: %p", vid, pid);
            mMatchTable[i].wVendorId = vid;
            mMatchTable[i+1].wProductId = pid;
        }
    }
    

    return entries;

}


/*!
 * Usb::isValidDevice 
 *
 * Description:
 * Checks if the handle matches any in our device list
 *
 * @param * device is of type UsbDevice 
 *
 * @returns bool 
 */
bool Usb::isValidDevice( UsbDevice * device )
{
    bool found = false;

    // Go through all the devices and test if this one is in the list.
    QList<UsbDevice *>::iterator it;
    for ( it = mDeviceList.begin(); it != mDeviceList.end(); ++it )
    {
        if ( (*it)->mhDevice == device->mhDevice )
        {
            found = true;
            break;
        }
    }

    return found;
}
